use crate::tree::t_exp::{Exp, ThreeAddrExp};
use crate::tree::t_fn::{Block, BlockType};
static TEMP_IDENT_PRE: &str = "_ti_";
pub fn visit(block: syn::Block, block_ty: BlockType) -> Vec<Block> {
    let mut bbns = Vec::<Block>::new();
    let temp_ident_counter: usize = 0;

    let (mut bbn_items, has_return) = extract_block(block, &mut bbns, temp_ident_counter, block_ty);
    let bbn_ty = if has_return {
        BlockType::FnRet //NOT used yet
    } else {
        BlockType::Fn
    };
    close_bbn(&mut bbns, &mut bbn_items, bbn_ty);
    bbns
}

fn extract_block(
    block: syn::Block,
    bbns: &mut Vec<Block>,
    mut temp_ident_counter: usize,
    block_ty: BlockType,
) -> (Vec<Exp>, bool) {
    let mut bbn_items = Vec::new();
    let mut has_return = false;
    //let stmts_count = block.stmts.len();
    for (_, stmt_o) in block.stmts.iter().enumerate() {
        let stmt = stmt_o.clone();
        match stmt {
            syn::Stmt::Local(local) => {
                let ident = match local.pat {
                    syn::Pat::Ident(pat_ident) => pat_ident.ident.to_string(),
                    _ => "unknown".to_string(),
                };
                if let Some(local_init) = local.init {
                    let sub_bbn_items =
                        extract_exp_local(local_init, ident, &mut temp_ident_counter);

                    bbn_items.extend(sub_bbn_items);
                }
            }
            syn::Stmt::Expr(expr, _) => {
                match expr {
                    syn::Expr::If(expr_if) => {
                        let (if_block, else_block, condition) =
                            extract_exp_if(expr_if, &mut temp_ident_counter);
                        match condition {
                            Exp::Assign(lval, _) => {
                                bbn_items.push(Exp::SwitchInt(lval));
                            }
                            Exp::ThreeAddrExp(three_addr) => {
                                let switch_c = three_addr.result.clone();
                                bbn_items.push(Exp::ThreeAddrExp(three_addr));
                                bbn_items.push(Exp::SwitchInt(switch_c));
                            }
                            _ => {}
                        }
                        close_bbn(bbns, &mut bbn_items, BlockType::Fn);
                        bbn_items.extend(if_block.bbn_items);
                        close_bbn(bbns, &mut bbn_items, if_block.bbn_ty);
                        if let Some(else_block) = else_block {
                            bbn_items.extend(else_block.bbn_items);
                            close_bbn(bbns, &mut bbn_items, else_block.bbn_ty);
                        }
                    }
                    syn::Expr::Return(expr_ret) => {
                        let (sub_bbn_items, return_val_ty) =
                            extract_exp_ret(expr_ret, &mut temp_ident_counter, block_ty.clone());
                        if return_val_ty == "path" {
                            let ret_exp = sub_bbn_items.last().unwrap().clone();
                            if let Exp::Assign(_, rval) = ret_exp {
                                if let Some(pos) = bbn_items.iter().rposition(|exp| {
                                    if let Exp::ThreeAddrExp(three_addr) = exp {
                                        three_addr.result == rval
                                    } else {
                                        false
                                    }
                                }) {
                                    if let Exp::ThreeAddrExp(ref mut three_addr) = bbn_items[pos] {
                                        three_addr.result = "_0".to_string();
                                    }
                                }
                            }
                        } else {
                            bbn_items.extend(sub_bbn_items);
                        }
                        has_return = true;
                    }
                    // syn::Expr::Binary(expr_binary) => {
                    //     if stmts_count-1 ==i && block_ty == BlockType::FnRet{
                    //     extract_binary(
                    //         "_0".to_string(),
                    //         &mut bbn_items,
                    //         &expr_binary,
                    //         &mut temp_ident_counter,
                    //     );
                    //     }
                    // }
                    _ => { /* Handle other expression types if needed */ }
                }
            }
            _ => {}
        }
    }
    (bbn_items, has_return)
}

fn close_bbn(bbns: &mut Vec<Block>, bbn_items: &mut Vec<Exp>, bbn_ty: BlockType) {
    bbns.push(Block {
        bbn_name: format!("bb{}", &bbns.iter().count()),
        bbn_items: bbn_items.clone(),
        bbn_ty,
    });
    bbn_items.clear();
}

fn extract_exp_if(
    if_expr: syn::ExprIf,
    temp_ident_counter: &mut usize,
) -> (Block, Option<Block>, Exp) {
    let mut bbn_exps = Vec::<Exp>::new();
    //condition
    let condition = extract_if_condition(if_expr.clone(), temp_ident_counter);
    let mut temp_sub_bbns = Vec::<Block>::new();

    let if_block_items = extract_block(
        if_expr.then_branch,
        &mut temp_sub_bbns,
        *temp_ident_counter,
        BlockType::If,
    );
    bbn_exps.extend(if_block_items.0);
    let if_block_type = if if_block_items.1 {
        BlockType::IfRet
    } else {
        BlockType::If
    };
    let has_else = if_expr.else_branch.is_some();
    bbn_exps.push(Exp::Goto(if_block_type.clone(), has_else));

    let if_block = Block {
        bbn_name: "if_block".to_string(),
        bbn_items: bbn_exps.clone(),
        bbn_ty: if_block_type,
    };
    bbn_exps.clear();

    let else_block = if let Some(else_branch) = if_expr.else_branch {
        let else_block_items: (Vec<Exp>, bool) = match *else_branch.1 {
            syn::Expr::Block(ref expr_block) => extract_block(
                expr_block.block.clone(),
                &mut temp_sub_bbns,
                *temp_ident_counter,
                BlockType::Else,
            ),
            _ => (Vec::new(), false),
        };
        bbn_exps.extend(else_block_items.0);
        let else_block_type = if else_block_items.1 {
            BlockType::ElseRet
        } else {
            BlockType::Else
        };
        bbn_exps.push(Exp::Goto(else_block_type.clone(), false));
        let else_block = Block {
            bbn_name: "else_block".to_string(),
            bbn_items: bbn_exps.clone(),
            bbn_ty: else_block_type,
        };
        Some(else_block)
    } else {
        None
    };

    (if_block, else_block, condition)
}

fn extract_if_condition(if_expr: syn::ExprIf, temp_ident_counter: &mut usize) -> Exp {
    match *if_expr.cond {
        syn::Expr::Binary(ref expr_binary) => {
            let mut temp_bbn_items = Vec::<Exp>::new();
            *temp_ident_counter += 1;
            let temp_ident = format!("{}_{}", TEMP_IDENT_PRE, *temp_ident_counter);
            extract_binary(
                temp_ident.clone(),
                &mut temp_bbn_items,
                expr_binary,
                temp_ident_counter,
            );
            let three_addr = temp_bbn_items.pop().unwrap(); //Don't need store condition exp in if block
            return three_addr;
        }
        syn::Expr::Lit(expr_lit) => {
            let mut a = Exp::Assign("0".to_string(), "0".to_string());
            if let syn::Lit::Int(lit_int) = &expr_lit.lit {
                let value = lit_int.base10_parse::<i32>().unwrap().to_string();
                a = Exp::Assign(value.to_string(), value.to_string())
            }
            return a;
        }
        syn::Expr::Path(ref expr_path) => {
            let mut a = Exp::Assign("_0".to_string(), "0".to_string());

            if let Some(ident_path) = expr_path.path.get_ident() {
                a = Exp::Assign(ident_path.clone().to_string(), ident_path.to_string())
            }
            return a;
        }
        _ => {
            return Exp::Assign("".to_string(), "".to_string());
        }
    };
}

fn extract_exp_ret(
    ret_expr: syn::ExprReturn,
    temp_ident_counter: &mut usize,
    block_ty: BlockType,
) -> (Vec<Exp>, String) {
    let mut bbn_exps = Vec::<Exp>::new();
    let mut return_val_ty = String::new();
    match ret_expr.expr {
        Some(expr) => match *expr {
            syn::Expr::Lit(expr_lit) => {
                if let syn::Lit::Int(lit_int) = &expr_lit.lit {
                    let value = lit_int.base10_parse::<i32>().unwrap();
                    let assign = Exp::Assign("_0".to_string(), value.to_string());
                    bbn_exps.push(assign);
                }
            }
            syn::Expr::Path(expr_path) => {
                if let Some(ident_path) = expr_path.path.get_ident() {
                    // if block_ty == BlockType::Fn {
                    //     //in case of fn without return type
                    //     let assign = Exp::Assign("_0".to_string(), ident_path.to_string());
                    //     bbn_exps.push(assign);
                    // }

                    let assign = Exp::Assign("_0".to_string(), ident_path.to_string());
                    bbn_exps.push(assign);
                    return_val_ty = "path".to_string();
                    // let ret_pre_reg = Exp::RetPreReg(ident_path.to_string());
                    // bbn_exps.push(ret_pre_reg);
                }
            }
            syn::Expr::Binary(expr_binary) => {
                extract_binary(
                    "_0".to_string(),
                    &mut bbn_exps,
                    &expr_binary,
                    temp_ident_counter,
                );
            }
            _ => {}
        },
        None => {}
    }
    (bbn_exps, return_val_ty)
}

fn extract_exp_local(
    init_expr: syn::LocalInit,
    ident: String,
    temp_ident_counter: &mut usize,
) -> Vec<Exp> {
    let mut bbn_exps = Vec::<Exp>::new();
    match init_expr.expr.as_ref() {
        syn::Expr::Lit(expr_lit) => {
            if let syn::Lit::Int(lit_int) = &expr_lit.lit {
                let value = lit_int.base10_parse::<i32>().unwrap();
                let assign = Exp::Assign(ident.clone(), value.to_string());
                bbn_exps.push(assign);
            }
        }
        syn::Expr::Path(expr_path) => {
            if let Some(ident_path) = expr_path.path.get_ident() {
                let assign = Exp::Assign(ident.clone(), ident_path.to_string());
                bbn_exps.push(assign);
            }
        }
        syn::Expr::Binary(expr_binary) => {
            extract_binary(ident, &mut bbn_exps, expr_binary, temp_ident_counter);
        }
        _ => {}
    }
    bbn_exps
}

fn extract_binary(
    ident: String,
    bbn_exps: &mut Vec<Exp>,
    expr_binary: &syn::ExprBinary,
    temp_ident_counter: &mut usize,
) {
    let left = match *expr_binary.left {
        syn::Expr::Lit(ref left_lit) => {
            if let syn::Lit::Int(ref lit_int) = left_lit.lit {
                lit_int.base10_parse::<i32>().unwrap().to_string()
            } else {
                "0".to_string()
            }
        }
        syn::Expr::Path(ref left_path) => {
            if let Some(ident_path) = left_path.path.get_ident() {
                ident_path.to_string()
            } else {
                "unknown".to_string()
            }
        }
        syn::Expr::Binary(ref left_binary) => {
            *temp_ident_counter += 1;
            let temp_ident = format!("{}_{}", TEMP_IDENT_PRE, *temp_ident_counter);
            extract_binary(
                temp_ident.clone(),
                bbn_exps,
                left_binary,
                temp_ident_counter,
            );
            temp_ident
        }
        _ => "unknown".to_string(),
    };
    let right = match *expr_binary.right {
        syn::Expr::Lit(ref right_lit) => {
            if let syn::Lit::Int(ref lit_int) = right_lit.lit {
                lit_int.base10_parse::<i32>().unwrap().to_string()
            } else {
                "0".to_string()
            }
        }
        syn::Expr::Path(ref right_path) => {
            if let Some(ident_path) = right_path.path.get_ident() {
                ident_path.to_string()
            } else {
                "unknown".to_string()
            }
        }

        syn::Expr::Binary(ref right_binary) => {
            *temp_ident_counter += 1;
            let temp_ident = format!("{}_{}", TEMP_IDENT_PRE, *temp_ident_counter);
            extract_binary(
                temp_ident.clone(),
                bbn_exps,
                right_binary,
                temp_ident_counter,
            );
            temp_ident
        }
        _ => "unknown".to_string(),
    };
    let op = match expr_binary.op {
        syn::BinOp::Add(_) => "Add".to_string(),
        syn::BinOp::Sub(_) => "Sub".to_string(),
        syn::BinOp::Mul(_) => "Mul".to_string(),
        syn::BinOp::Gt(_) => "Gt".to_string(),
        syn::BinOp::Ge(_) => "Ge".to_string(),
        syn::BinOp::Lt(_) => "Lt".to_string(),
        syn::BinOp::Le(_) => "Le".to_string(),
        syn::BinOp::Eq(_) => "Eq".to_string(),
        //syn::BinOp::Div(_) => "/".to_string(),
        _ => "unknown".to_string(),
    };
    let three_addr_exp = ThreeAddrExp {
        result: ident.clone(),
        op,
        left,
        right,
    };
    bbn_exps.push(Exp::ThreeAddrExp(three_addr_exp));
}
